package in

import (
	"context"
	"log"
	"strings"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var (
	GlobalMongoBottle, _ = NewMongoBottle("mongodb://localhost:27017")
	GlobalOperChannel    = make(chan int, 20)
)

type MongoBottle struct {
	Bottle
}

func NewMongoBottle(mongoUrl string) (bottle *MongoBottle, err error) {
	bottle = new(MongoBottle)
	bottle.Data = mongoUrl
	client, clientctx := GetMongoClient(mongoUrl)
	bottle.Engine = G{
		"client": client,
		"ctx":    clientctx,
	}
	bottle.Tp = "mongodb"
	bottle.IsDB = true

	return
}

func (mdb *MongoBottle) Query(oper *Oper) (datas Datas) {
	defer func() {
		if len(GlobalOperChannel) > 0 {
			<-GlobalOperChannel
		}
	}()
	GlobalOperChannel <- 0
	filter := oper.Filter
	dbAndcollection := strings.SplitN(oper.Name, ".", 2)
	client := mdb.Engine.(G)["client"].(*mongo.Client)
	ctx := mdb.Engine.(G)["ctx"].(context.Context)

	Db := dbAndcollection[0]
	collection := client.Database(Db).Collection(dbAndcollection[1])
	AppendFunc := func(d map[string]interface{}) {
		datas = append(datas, d)
	}
	startAt := oper.Page * oper.Len
	endAt := startAt + oper.Len
	// log.Println("to find", dbAndcollection, filter)
	cur, err := collection.Find(ctx, filter)
	if err != nil {
		log.Println("find mongo error:", err)
	}
	defer cur.Close(ctx)
	nowAt := 0
	for cur.Next(ctx) {
		var result bson.M
		err := cur.Decode(&result)
		if err != nil {
			log.Println("error in query next:", err)
		}
		// do something with result....
		if nowAt >= startAt && nowAt < endAt {
			AppendFunc(result)
		}
		nowAt++
	}
	if err := cur.Err(); err != nil {
		log.Println(err)
	}
	if !mdb.NoCached {
		SaveInCache(mdb.Data+"/"+oper.Name, datas)
	}
	return
}

func (mdb *MongoBottle) Desc(oper *Oper) (keys S) {
	name := oper.Name
	client := mdb.Engine.(G)["client"].(*mongo.Client)
	ctx := mdb.Engine.(G)["ctx"].(context.Context)
	var err error
	if name == "" {
		keys, err = client.ListDatabaseNames(ctx, G{})
	} else {
		keys, err = client.Database(name).ListCollectionNames(ctx, G{})
	}
	if err != nil {
		log.Println("Desc mongodb:", err)
	}
	return
}

func (mdb *MongoBottle) GetTp() string {
	return mdb.Tp
}
func (mdb *MongoBottle) GetName() string {
	return mdb.Data
}

// GetMongoClient Get a mongo client
func GetMongoClient(mongoURI string) (*mongo.Client, context.Context) {
	var err error
	if mongoURI == "" {
		mongoURI = "mongodb://localhost:27017"
	}
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	// if err != nil {
	// 	log.Fatal("context mongo error:", err)
	// }
	client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
	if err != nil {
		log.Fatal("connect mongo error:", err)
	}
	return client, ctx
}

// EntryMany save many data
func EntryMany(db, col string, messages []interface{}) (success int64) {
	client := GlobalMongoBottle.Engine.(G)["client"].(*mongo.Client)
	ctx := GlobalMongoBottle.Engine.(G)["ctx"].(context.Context)

	collection := client.Database(db).Collection(col)
	inCollection, err := collection.InsertMany(ctx, messages)
	if err != nil {
		log.Println("insert mongo error:", err)
	}
	success = int64(len(inCollection.InsertedIDs))
	return
}

// FindSome to query some doc from collection by map[string]interface{}
func FindSome(db, col string, filter map[string]interface{}, callback func(doc bson.M, nowAt int)) (err error) {
	client := GlobalMongoBottle.Engine.(G)["client"].(*mongo.Client)
	ctx := GlobalMongoBottle.Engine.(G)["ctx"].(context.Context)

	collection := client.Database(db).Collection(col)
	cur, err := collection.Find(ctx, filter)
	if err != nil {
		log.Println("find mongo error:", err)
	}
	defer cur.Close(ctx)
	nowAt := 0
	for cur.Next(ctx) {
		var result bson.M
		err := cur.Decode(&result)
		if err != nil {
			log.Println("error in query next:", err)
		}
		// do something with result....
		if callback != nil {
			callback(result, nowAt)
		}
		nowAt++
	}
	if err := cur.Err(); err != nil {
		log.Println(err)
	}
	return
}

// DeleteSome delete some datas
func DeleteSome(db, col string, filter map[string]interface{}) (success int64) {
	client := GlobalMongoBottle.Engine.(G)["client"].(*mongo.Client)
	ctx := GlobalMongoBottle.Engine.(G)["ctx"].(context.Context)

	collection := client.Database(db).Collection(col)
	inCollection, err := collection.DeleteMany(ctx, filter)
	if err != nil {
		log.Println("dict -> bson.m error:", err)
	}
	success = inCollection.DeletedCount
	return
}
